package org.simplextensions.graph;

import java.util.*;

/**
 * 
 * @author Tomasz Krzyzak, <a
 *         href="mailto:tomasz.krzyzak@gmail.com">tomasz.krzyzak@gmail.com</a>
 * @since 2010-04-01 22:53:56
 */
public class GraphNode {

	private final Object object;
	private final String id;
	private boolean fullyConnected = false;

	private Map<String, GraphLink> outgoingLinks = new HashMap<String, GraphLink>();
	private Map<String, GraphLink> incomingLinks = new HashMap<String, GraphLink>();
	private final Graph graph;

	Map<String, GraphLink> addOutgoing(String... endNodeIds) {
		Map<String, GraphLink> result = new HashMap<String, GraphLink>();
		if (endNodeIds.length > 0) {
			for (String endNodeId : endNodeIds) {
				if (outgoingLinks.containsKey(endNodeId))
					continue;

				GraphLink value = new GraphLink(this, endNodeId);
				this.outgoingLinks.put(endNodeId, value);
				result.put(endNodeId, value);
			}
			//setFullyConnected(false);
		} else {
			checkFullyConnected();
		}
		return result;
	}

	void checkFullyConnected() {
		boolean tempFC = true;
		for (GraphLink gl : outgoingLinks.values()) {
			if (gl.getEndNode() == null || !gl.getEndNode().isFullyConnected()) {
				tempFC = false;
				break;
			}
		}
		if (isFullyConnected() != tempFC)
			setFullyConnected(tempFC);
	}

	private void setFullyConnected(boolean fullyConnected) {
		this.fullyConnected = fullyConnected;

		HashSet<GraphNode> nodes = new HashSet<GraphNode>();
		if (!fullyConnected) {
			graph.notifyNodeLostConnections(this, nodes);
		} else {
			graph.notifyNodeFullyConnected(this, nodes);
		}

		for (GraphLink gl : incomingLinks.values()) {
			if (!fullyConnected) {
				gl.getStartNode().setFullyConnected(false);
			} else {
				gl.getStartNode().checkFullyConnected();
			}
		}
	}

	void innerAddIncoming(GraphLink graphLink) {
		GraphNode endNode = graphLink.getEndNode();
		if (endNode != null) {
			throw new IllegalStateException(graphLink.toString());
		}
		this.incomingLinks.put(graphLink.getStartNode().getId(), graphLink);
	}

	void innerRemoveIncoming(GraphLink link) {
		this.incomingLinks.remove(link);
	}

	public GraphNode(Graph graph, String id, Object elem) {
		this.graph = graph;
		this.id = id;
		this.object = elem;
	}

	public Object getObject() {
		return object;
	}

	public String getId() {
		return id;
	}

	public String toString() {
		return new StringBuilder().append("id: ").append(id).append(", data: ").append(object).toString();
	}

	public Collection<GraphLink> getOutgoingLinks() {
		return Collections.unmodifiableCollection(outgoingLinks.values());
	}

	public Collection<GraphLink> getIncommingLinks() {
		return Collections.unmodifiableCollection(incomingLinks.values());
	}

	/**
	 * checks whether node is fully connected
	 * 
	 * @return <b>true</b> when all links have defined endNode, <br />
	 *         <b>false</b> otherwise
	 */
	public boolean isFullyConnected() {
		/*
		 * boolean fullyConnected = true;
		 * 
		 * Collection<GraphLink> set2 = getOutgoingLinks(); // check all links
		 * of startNode of current link for (GraphLink gl : set2) { if
		 * (gl.getEndNode() == null || !gl.getEndNode().isFullyConnected()) {
		 * fullyConnected = false; break; } } return fullyConnected;
		 */
		return fullyConnected;
	}

}
